import * as vscode from 'vscode';
import * as resource from './resourceTree';
import * as monitor from './monitorTree';
import * as tools from './extendToolsTree';
import * as debug from './debug';
import * as childProcess from 'child_process';

export function RegeisterCommands(context: vscode.ExtensionContext, resourceTree: resource.ROS2ExecutableProvider, monitorTree: monitor.MonitorProvider, toolsTree: tools.EntendToolsProvider) {
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.run', ros2RunCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.launch', ros2LaunchCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.build', (uri: vscode.Uri) => ros2BuildSelectedPackagesCommand(uri, resourceTree)));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.build_all', async () => { await ros2BuildAllPackagesCommand(); resourceTree.refresh(); }));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.build_last', buildLastCommad));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.debug', ros2DebugCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.clean', () => { ros2CleanCommand(); resourceTree.refresh(); }));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.add_args', (uri: vscode.Uri) => addArgsCommand(uri, resourceTree)));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.remove_args', (uri: vscode.Uri) => removeArgsCommand(uri, resourceTree)));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.modify_args', (uri: vscode.Uri) => modifyArgsCommand(uri, resourceTree)));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.refresh_resource', () => resourceTree.refresh()));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.refresh_monitor', () => monitorTree.refresh()));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.refresh_tools', () => toolsTree.refresh()));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.create', ros2CreatePackageCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.change_symlink_install', changeSymlinkInstallCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.reset_params', () => resourceTree.reset()));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.run_tool', ros2RunToolCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.run_last', ros2RunLastCommand));
    context.subscriptions.push(vscode.commands.registerCommand('ros2-helper.open_file', openFileCommand));
}

let terminals: Map<string, vscode.Terminal> = new Map();
type LastCommand = [string, string]//tag,command
let lastBuildCommand: LastCommand | undefined;
let lastRunCommand: LastCommand | undefined;

async function executeCommandInTerminal(command: string, tag: string = 'ROS2', autoQuit: boolean = false, reuseID?: string) {
    let terminal: vscode.Terminal | undefined;
    if (reuseID) {
        terminal = terminals.get(reuseID);
        if (!terminal || terminal.exitStatus !== undefined) {
            terminal = vscode.window.createTerminal(tag);
            terminals.set(reuseID, terminal);
        }
    }
    else {
        terminal = vscode.window.createTerminal(tag);
    }
    terminal.sendText(command);
    if (autoQuit) {
        terminal.sendText('exit');
    }
    terminal.show(true);
}

export function executeCommandInChildProcess(command: string): Promise<{ stdout: string, stderr: string }> {
    return new Promise((resolve, reject) => {
        childProcess.exec(command, (error, stdout: string, stderr: string) => {
            if (error) {
                reject(error);
            } else {
                resolve({ stdout, stderr });
            }
        });
    });
}

async function ros2RunCommand(uri: vscode.Uri) {
    let packageName: string | undefined, executableName: string | undefined;
    if (uri === undefined) {
        packageName = await vscode.window.showInputBox({ prompt: 'Enter the ROS2 package name' });
        executableName = await vscode.window.showInputBox({ prompt: 'Enter the executable name' });

        if (!packageName || !executableName) {
            vscode.window.showErrorMessage('Package name and executable name are required.');
            return;
        }
    }
    else if (uri instanceof resource.Ros2Executable) {
        packageName = (uri as resource.Ros2Executable).packageName();
        executableName = (uri as resource.Ros2Executable).executableName();
    }
    else {
        vscode.window.showErrorMessage('Invalid URI,BUG Detected');
        return;
    }
    let params = (uri as resource.Ros2Executable).params;
    let remaps = (uri as resource.Ros2Executable).remaps;
    let args: string[] = [];
    if (params.length > 0) {
        args = args.concat(params.map(param => `-p ${param.paramName()}:=${param.paramValue()}`));
    }
    if (remaps.length > 0) {
        args = args.concat(remaps.map(remap => `-r ${remap.originalTopic()}:=${remap.newTopic()}`));
    }
    let command = `ros2 run ${packageName} ${executableName} --ros-args ${args.join(' ')}`;
    executeCommandInTerminal(command, executableName, false);
    lastRunCommand = [executableName, command];
}

async function ros2LaunchCommand(uri: vscode.Uri) {
    let packageName: string | undefined, launchFile: string | undefined;
    if (uri === undefined) {
        packageName = await vscode.window.showInputBox({ prompt: 'Enter the ROS2 package name' });
        launchFile = await vscode.window.showInputBox({ prompt: 'Enter the ROS2 launch file' });

        if (!packageName || !launchFile) {
            vscode.window.showErrorMessage('Package name and launch file name are required.');
            return;
        }
    }
    else if (uri instanceof resource.Ros2LaunchFile) {
        packageName = (uri as resource.Ros2LaunchFile).packageName();
        launchFile = (uri as resource.Ros2LaunchFile).launchFileName();
    }
    else {
        vscode.window.showErrorMessage('Invalid URI,BUG Detected');
        return;
    }
    let params = (uri as resource.Ros2LaunchFile).params;
    let args: string = '';
    if (params.length > 0) {
        args = params.map(param => `${param.paramName()}:=${param.paramValue()}`).join(' ');
    }
    let command = `ros2 launch ${packageName} ${launchFile} ${args}`;
    executeCommandInTerminal(command, launchFile, false);
    lastRunCommand = [launchFile, command];
}

async function ros2DebugCommand(uri: vscode.Uri) {
    if (uri === undefined) {
        vscode.window.showErrorMessage('No executable selected.');
        return;
    }
    if (uri instanceof resource.Ros2Executable) {
        let executable = uri as resource.Ros2Executable;
        let params = (uri as resource.Ros2Executable).params;
        let remaps = (uri as resource.Ros2Executable).remaps;
        let args: string[] = ['--ros-args'];
        if (params.length > 0) {
            args = args.concat(params.flatMap(param => ['-p', `${param.paramName()}:=${param.paramValue()}`]));
        }
        if (remaps.length > 0) {
            args = args.concat(remaps.flatMap(remap => ['-r', `${remap.originalTopic()}:=${remap.newTopic()}`]));
        }

        if (executable.type === resource.ExecutableType.Bin) {
            debug.startDebugOnCppExecutable(executable.executableName(), executable.packageName(), args);
        }
        else if (executable.type === resource.ExecutableType.Script) {
            debug.startDebugOnPythonExecutable(executable.executableName(), executable.packageName(), args);
        }
    }
    else if (uri instanceof resource.Ros2LaunchFile) {
        let launchFile = uri as resource.Ros2LaunchFile;
        let params = launchFile.params;
        let args: string[] = ['--ros-args'];
        if (params.length > 0) {
            args = args.concat(params.flatMap(param => ['-p', `${param.paramName()}:=${param.paramValue()}`]));
        }
        debug.startDebugOnLaunchFile(launchFile.launchFileName(), launchFile.packageName(), args);
    }
}

async function ros2BuildSelectedPackagesCommand(uri: vscode.Uri, resourceTree: resource.ROS2ExecutableProvider) {
    let packageNames: string[] = [];
    if (uri == undefined) {
        let allPackageNames = await resourceTree.getPackageNames();
        packageNames = await vscode.window.showQuickPick(allPackageNames, {
            canPickMany: true,
            placeHolder: 'Select packages to build'
        }) || [];
        if (packageNames.length === 0) {
            vscode.window.showErrorMessage('No package selected.');
            return;
        }
    }
    else if (uri instanceof resource.Ros2Package) {
        packageNames = [(uri as resource.Ros2Package).packageName()];
    }
    let symlinkInstall = vscode.workspace.getConfiguration().get<boolean>('ros2-helper.use_symlink_install');
    let exportCommand = vscode.workspace.getConfiguration().get<boolean>('ros2-helper.export_compile_commands');
    let buildtypeString = vscode.workspace.getConfiguration().get<string>('ros2-helper.build_type');
    let buildCommand = `colcon build ${symlinkInstall ? '--symlink-install' : ''} --packages-up-to ${packageNames.join(' ')}   ${exportCommand ? '--cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON' : ''} -DCMAKE_BUILD_TYPE=${buildtypeString}`;
    executeCommandInTerminal(buildCommand, 'ROS2 Build', false, 'compile');
    lastBuildCommand = [packageNames.join('+'), buildCommand];
    resourceTree.refresh();
}

async function ros2BuildAllPackagesCommand() {
    let symlinkInstall = vscode.workspace.getConfiguration().get<boolean>('ros2-helper.use_symlink_install');
    let exportCommand = vscode.workspace.getConfiguration().get<boolean>('ros2-helper.export_compile_commands');
    let buildtypeString = vscode.workspace.getConfiguration().get<string>('ros2-helper.build_type');
    let excludes = vscode.workspace.getConfiguration().get<Array<string>>('ros2-helper.exclude_packages');
    if (excludes == undefined)
        excludes = [];
    let buildCommand = `colcon build ${symlinkInstall ? '--symlink-install' : ''} ${excludes?.length > 0 ? `--packages-skip ${excludes.join(' ')}` : ' '} ${exportCommand ? '--cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON' : ''} -DCMAKE_BUILD_TYPE=${buildtypeString}`;
    executeCommandInTerminal(buildCommand, 'ROS2 Build', false, 'compile');
    lastBuildCommand = ["build all", buildCommand];
}

async function ros2CleanCommand() {
    executeCommandInTerminal("rm -rf build\n rm -rf install \n rm -rf log \n rm -rf .cache", 'ROS2 Clean', true);
}

async function addArgsCommand(uri: vscode.Uri, resourceTree: resource.ROS2ExecutableProvider) {
    if (!(uri instanceof resource.Ros2Executable)) {
        vscode.window.showErrorMessage('Invalid URI, uri must be ros2-executable, BUG Detected');
        return;
    }
    let executable = uri as resource.Ros2Executable;
    let runable = new resource.Runable(executable.packageName(), executable.executableName()).asKey();
    let Arguments = resourceTree.datas.get(runable);
    if (!Arguments) {
        Arguments = new resource.Arguments();
        resourceTree.datas.set(runable, Arguments);
    }
    if (uri instanceof resource.Ros2Executable) {
        let select = await vscode.window.showQuickPick(['add parma config', 'add topic remap'], { placeHolder: 'Select the type of args to add' });
        if (select === 'add parma config') {
            let name = await vscode.window.showInputBox({ prompt: 'Enter the name of the parma' });
            let value = await vscode.window.showInputBox({ prompt: 'Enter the value of the parma' });
            if (!name || !value) {
                vscode.window.showErrorMessage('Param name and value are required.');
                return;
            }
            Arguments.params.push({ paramName: name, value: value });
        }
        else if (select === 'add topic remap') {
            let from = await vscode.window.showInputBox({ prompt: 'Enter the original topic name' });
            let to = await vscode.window.showInputBox({ prompt: 'Enter the new topic name' });
            if (!from || !to) {
                vscode.window.showErrorMessage('Original topic name and new topic name are required.');
                return;
            }
            Arguments.topicRemaps.push({ original: from, new: to });
        }
    }
    resourceTree.save();
    resourceTree.refresh();
}

async function removeArgsCommand(uri: vscode.Uri, resourceTree: resource.ROS2ExecutableProvider) {
    if (!(uri instanceof resource.ParamsNode || uri instanceof resource.TopicRemapNode)) {
        vscode.window.showErrorMessage('Invalid URI, uri must be ros2-executable or ros2-launch-file, BUG Detected');
        return;
    }

    let runable;
    if (uri.parent instanceof resource.Ros2Executable) {
        let executable = uri.parent as resource.Ros2Executable;
        runable = new resource.Runable(executable.packageName(), executable.executableName()).asKey();
    }
    else if (uri.parent instanceof resource.Ros2LaunchFile) {
        let executable = uri.parent as resource.Ros2LaunchFile;
        runable = new resource.Runable(executable.packageName(), executable.launchFileName()).asKey();
    }
    else {
        vscode.window.showErrorMessage('Invalid URI,BUG Detected');
        return;
    }
    let Arguments = resourceTree.datas.get(runable);
    if (!Arguments) {
        vscode.window.showErrorMessage('No args found. failed to remove.');
        return;
    }

    if (uri instanceof resource.ParamsNode) {
        let parmasNode = uri as resource.ParamsNode;
        Arguments.params = Arguments.params.filter(param => param.paramName !== parmasNode.paramName());
    } else if (uri instanceof resource.TopicRemapNode) {
        let remapNode = uri as resource.TopicRemapNode;
        Arguments.topicRemaps = Arguments.topicRemaps.filter(remap => remap.original !== remapNode.originalTopic());
    }
    resourceTree.save();
    resourceTree.refresh();
}

async function modifyArgsCommand(uri: vscode.Uri, resourceTree: resource.ROS2ExecutableProvider) {
    if (!(uri instanceof resource.ParamsNode || uri instanceof resource.TopicRemapNode)) {
        vscode.window.showErrorMessage('Invalid URI, uri must be ros2-executable or ros2-launch-file, BUG Detected');
        return;
    }
    let runable;
    let Arguments;
    if (uri.parent instanceof resource.Ros2Executable) {
        let executable = uri.parent as resource.Ros2Executable;
        runable = new resource.Runable(executable.packageName(), executable.executableName()).asKey();
        Arguments = resourceTree.datas.get(runable);
        if (!Arguments) {
            vscode.window.showErrorMessage('No args found.failed to modify.');
            return;
        }
    }
    else if (uri.parent instanceof resource.Ros2LaunchFile) {
        let executable = uri.parent as resource.Ros2LaunchFile;
        runable = new resource.Runable(executable.packageName(), executable.launchFileName()).asKey();
        Arguments = resourceTree.datas.get(runable);
        if (!Arguments) {
            resourceTree.datas.set(runable, new resource.Arguments());
            return;
        }
        Arguments = resourceTree.datas.get(runable);
        if (!Arguments) {
            vscode.window.showErrorMessage('No args found. And failed to add new args for launch file.');
            return;
        }
    }
    else {
        vscode.window.showErrorMessage('Invalid URI,BUG Detected');
        return;
    }

    if (uri instanceof resource.ParamsNode) {
        let parmasNode = uri as resource.ParamsNode;
        let newvalue = await vscode.window.showInputBox({
            prompt: `Enter the new value of the parma ${parmasNode.paramName()} :`,
            value: parmasNode.paramValue()
        });
        if (!newvalue) {
            vscode.window.showWarningMessage('ignore empty value');
            return;
        }
        let index = Arguments.params.findIndex(param => param.paramName === parmasNode.paramName());
        if (index === -1 && uri.parent instanceof resource.Ros2LaunchFile) {
            Arguments.params.push({ paramName: parmasNode.paramName(), value: newvalue });
        } else {
            Arguments.params[index].value = newvalue;
        }
    } else if (uri instanceof resource.TopicRemapNode) {
        let remapNode = uri as resource.TopicRemapNode;
        let newvalue = await vscode.window.showInputBox({
            prompt: `Enter the new topic name remap from ${remapNode.originalTopic()} :`,
            value: remapNode.newTopic()
        });
        if (!newvalue) {
            vscode.window.showWarningMessage('ignore empty value');
            return;
        }
        let index = Arguments.topicRemaps.findIndex(remap => remap.original === remapNode.originalTopic());
        Arguments.topicRemaps[index].new = newvalue;
    }
    resourceTree.save();
    resourceTree.refresh();
}

async function changeSymlinkInstallCommand() {
    const cfg = vscode.workspace.getConfiguration();
    let enable = !cfg.get<boolean>('ros2-helper.use_symlink_install');
    await cfg.update('ros2-helper.use_symlink_install', enable, vscode.ConfigurationTarget.Workspace);
    vscode.window.showInformationMessage(`Symlink install is ${enable ? 'enabled' : 'disabled'}.`);
}

async function ros2CreatePackageCommand() {
    let packageName = await vscode.window.showInputBox({ prompt: 'Enter the ROS2 package name' });
    if (!packageName) {
        vscode.window.showErrorMessage('Package name is required.');
        return;
    }
    let buildType = await vscode.window.showQuickPick(['ament_cmake', 'ament_python', "combined"], { placeHolder: 'Select the build type' });
    if (!buildType || buildType === 'combined') {
        vscode.window.showErrorMessage('Build type is required. And combined is not supported yet.');
        return;
    }
    let dependencies = await vscode.window.showInputBox({ prompt: 'Enter the dependencies of the package (separated by space)' });
    let command = `ros2 pkg create ${packageName} --build-type ${buildType} ${dependencies?.trim().length == 0 ? '' : `--dependencies ${dependencies}`}`;
    executeCommandInTerminal(command, 'ROS2 Create Package', true);
}

async function ros2RunToolCommand(uri: vscode.Uri) {
    if (uri === undefined) {
        vscode.window.showErrorMessage('No executable selected.');
        return;
    }
    if (uri instanceof tools.ToolNode) {
        childProcess.exec((uri as tools.ToolNode).CommandString(), (error, stdout, stderr) => { })
            .addListener('exit', (code, signal) => {
                console.log(`child process exited with code ${code} and signal ${signal}`);
                vscode.window.showErrorMessage(`The tool exited with code ${code} and signal ${signal}.`);
            }).addListener('error', (err) => {
                console.log(`child process error: ${err}`);
            }).addListener('close', (code, signal) => {
                console.log(`child process closed with code ${code} and signal ${signal}`);
            });
    }
}

async function openFileCommand(uri: vscode.Uri) {
    try {
        const document = await vscode.workspace.openTextDocument(uri);
        await vscode.window.showTextDocument(document);
    } catch (err) {
        vscode.window.showErrorMessage(`can not open file : ${uri.fsPath}`);
    }
}

async function buildLastCommad() {
    if (!lastBuildCommand) {
        vscode.window.showErrorMessage('No last build command');
        return;
    }
    executeCommandInTerminal(lastBuildCommand[1], 'ROS2 Build', false, 'compile');
}

async function ros2RunLastCommand() {
    if (!lastRunCommand) {
        vscode.window.showErrorMessage('No last run command');
        return;
    }
    executeCommandInTerminal(lastRunCommand[1], lastRunCommand[0], false);
}

